// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef NET_DISK_CACHE_BLOCKFILE_STORAGE_BLOCK_INL_H_
#define NET_DISK_CACHE_BLOCKFILE_STORAGE_BLOCK_INL_H_

#include "net/disk_cache/blockfile/storage_block.h"

#include <stddef.h>
#include <stdint.h>

#include "base/hash.h"
#include "base/logging.h"
#include "net/disk_cache/blockfile/trace.h"

namespace disk_cache {

template <typename T>
StorageBlock<T>::StorageBlock(MappedFile* file,
    Addr address)
    : data_(NULL)
    , file_(file)
    , address_(address)
    , modified_(false)
    , own_data_(false)
    , extended_(false)
{
    if (address.num_blocks() > 1)
        extended_ = true;
    DCHECK(!address.is_initialized() || sizeof(*data_) == address.BlockSize());
}

template <typename T>
StorageBlock<T>::~StorageBlock()
{
    if (modified_)
        Store();
    DeleteData();
}

template <typename T>
void* StorageBlock<T>::buffer() const
{
    return data_;
}

template <typename T>
size_t StorageBlock<T>::size() const
{
    if (!extended_)
        return sizeof(*data_);
    return address_.num_blocks() * sizeof(*data_);
}

template <typename T>
int StorageBlock<T>::offset() const
{
    return address_.start_block() * address_.BlockSize();
}

template <typename T>
bool StorageBlock<T>::LazyInit(MappedFile* file,
    Addr address)
{
    if (file_ || address_.is_initialized()) {
        NOTREACHED();
        return false;
    }
    file_ = file;
    address_.set_value(address.value());
    if (address.num_blocks() > 1)
        extended_ = true;

    DCHECK(sizeof(*data_) == address.BlockSize());
    return true;
}

template <typename T>
void StorageBlock<T>::SetData(T* other)
{
    DCHECK(!modified_);
    DeleteData();
    data_ = other;
}

template <typename T>
void StorageBlock<T>::Discard()
{
    if (!data_)
        return;
    if (!own_data_) {
        NOTREACHED();
        return;
    }
    DeleteData();
    data_ = NULL;
    modified_ = false;
    extended_ = false;
}

template <typename T>
void StorageBlock<T>::StopSharingData()
{
    if (!data_ || own_data_)
        return;
    DCHECK(!modified_);
    data_ = NULL;
}

template <typename T>
void StorageBlock<T>::set_modified()
{
    DCHECK(data_);
    modified_ = true;
}

template <typename T>
void StorageBlock<T>::clear_modified()
{
    modified_ = false;
}

template <typename T>
T* StorageBlock<T>::Data()
{
    if (!data_)
        AllocateData();
    return data_;
}

template <typename T>
bool StorageBlock<T>::HasData() const
{
    return (NULL != data_);
}

template <typename T>
bool StorageBlock<T>::VerifyHash() const
{
    uint32_t hash = CalculateHash();
    return (!data_->self_hash || data_->self_hash == hash);
}

template <typename T>
bool StorageBlock<T>::own_data() const
{
    return own_data_;
}

template <typename T>
const Addr StorageBlock<T>::address() const
{
    return address_;
}

template <typename T>
bool StorageBlock<T>::Load()
{
    if (file_) {
        if (!data_)
            AllocateData();

        if (file_->Load(this)) {
            modified_ = false;
            return true;
        }
    }
    LOG(WARNING) << "Failed data load.";
    Trace("Failed data load.");
    return false;
}

template <typename T>
bool StorageBlock<T>::Store()
{
    if (file_ && data_) {
        data_->self_hash = CalculateHash();
        if (file_->Store(this)) {
            modified_ = false;
            return true;
        }
    }
    LOG(ERROR) << "Failed data store.";
    Trace("Failed data store.");
    return false;
}

template <typename T>
bool StorageBlock<T>::Load(FileIOCallback* callback,
    bool* completed)
{
    if (file_) {
        if (!data_)
            AllocateData();

        if (file_->Load(this, callback, completed)) {
            modified_ = false;
            return true;
        }
    }
    LOG(WARNING) << "Failed data load.";
    Trace("Failed data load.");
    return false;
}

template <typename T>
bool StorageBlock<T>::Store(FileIOCallback* callback,
    bool* completed)
{
    if (file_ && data_) {
        data_->self_hash = CalculateHash();
        if (file_->Store(this, callback, completed)) {
            modified_ = false;
            return true;
        }
    }
    LOG(ERROR) << "Failed data store.";
    Trace("Failed data store.");
    return false;
}

template <typename T>
void StorageBlock<T>::AllocateData()
{
    DCHECK(!data_);
    if (!extended_) {
        data_ = new T;
    } else {
        void* buffer = new char[address_.num_blocks() * sizeof(*data_)];
        data_ = new (buffer) T;
    }
    own_data_ = true;
}

template <typename T>
void StorageBlock<T>::DeleteData()
{
    if (own_data_) {
        if (!extended_) {
            delete data_;
        } else {
            data_->~T();
            delete[] reinterpret_cast<char*>(data_);
        }
        own_data_ = false;
    }
}

template <typename T>
uint32_t StorageBlock<T>::CalculateHash() const
{
    return base::Hash(reinterpret_cast<char*>(data_), offsetof(T, self_hash));
}

} // namespace disk_cache

#endif // NET_DISK_CACHE_BLOCKFILE_STORAGE_BLOCK_INL_H_
